// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System.Collections.Generic;

namespace SnapObjects.Data
{
    internal class BatchCommandBuilder
    {
        private readonly DataContext _dataContext;

        public BatchCommandBuilder(DataContext dataContext)
        {
            _dataContext = dataContext;
        }

        public IReadOnlyList<BatchCommandBuilderResult> BuildCommand(IReadOnlyList<IUpdatable> updatables)
        {
            var builderResult = new BuilderResult();

            builderResult.CommandBuilderContext = new CommandBuilderContext();
            builderResult.BatchCommandBuilderResult = new BatchCommandBuilderResult(builderResult.CommandBuilderContext);
            builderResult.NameGeneratorForModel = new ParameterNameGenerator("p_model");
            builderResult.NameGeneratorForSql = new ParameterNameGenerator("p_sql_");
            builderResult.ModelCommandBuilder = new ModelCommandBuilder(_dataContext, builderResult.CommandBuilderContext);
            builderResult.SqlCommandBuilder = new SqlCommandBuilder(_dataContext, builderResult.CommandBuilderContext);
            
            var commands = new List<BatchCommandBuilderResult>();

            commands.Add(builderResult.BatchCommandBuilderResult);

            foreach (var updatable in updatables)
            {
                builderResult.Updatable = updatable;

                if (updatable.UpdateType == UpdateType.ModelUpdate)
                {
                    this.BuildModelUpdatable(builderResult);
                }
                else
                {
                    this.BuildSqlUpdatable(builderResult);
                }

                if (!commands.Contains(builderResult.BatchCommandBuilderResult))
                {
                    commands.Add(builderResult.BatchCommandBuilderResult);
                }
            }

            return commands;
        }

        private void BuildModelUpdatable(BuilderResult builderResult)
        {
            ModelUpdateInfo modelUpdateInfo = null;

            var updatableModel = (UpdatableModel)builderResult.Updatable;

            modelUpdateInfo = new ModelUpdateInfo(
                updatableModel.ModelMeta.MappedTable,
                updatableModel.ModelMeta.MappedSchema,
                updatableModel,
                builderResult.NameGeneratorForModel.GenerateNext);

            if ((modelUpdateInfo.ModelState == ModelState.NewModified ||
                modelUpdateInfo.ModelState == ModelState.Modified) &&
                !modelUpdateInfo.HasWrite)
            {
                return;
            }

            var parameterCount = builderResult.ModelCommandBuilder.GetParameterCount(modelUpdateInfo);

            var isParameterExceedLimit = this.IsParameterExceedLimit(
                builderResult.BatchCommandBuilderResult, parameterCount);

            if (isParameterExceedLimit)
            {
                builderResult.Reset(_dataContext);
            }

            var resultsetMapping = builderResult.ModelCommandBuilder.BuildSql(modelUpdateInfo);

            builderResult.ModelCommandBuilder.BuildParameters(modelUpdateInfo);

            builderResult.BatchCommandBuilderResult.AddUpdatable(
                builderResult.Updatable, modelUpdateInfo, resultsetMapping);
        }

        private void BuildSqlUpdatable(BuilderResult builderResult)
        {
            SqlSyntaxParser parser = null;

            parser = builderResult.SqlCommandBuilder.GetSqlParser(
                (IUpdatableSql)builderResult.Updatable, builderResult.NameGeneratorForSql.GenerateNext);

            var parameterCount = parser.SqlParameters.Count;

            var isParameterExceedLimit = this.IsParameterExceedLimit(
                builderResult.BatchCommandBuilderResult, parameterCount);

            if (isParameterExceedLimit)
            {
                builderResult.Reset(_dataContext);
            }

            var resultsetMapping = builderResult.SqlCommandBuilder.BuildCommand(
                (IUpdatableSql)builderResult.Updatable, parser, builderResult.NameGeneratorForSql.GenerateNext);

            builderResult.BatchCommandBuilderResult.AddUpdatable(builderResult.Updatable, null, resultsetMapping);
        }

        private bool IsParameterExceedLimit(BatchCommandBuilderResult batchCommandBuilder, int parameterCount)
        {
            if (_dataContext.ContextOptions is DataContextOptions opts)
            {
                var maxCount = opts.MaxParameterCountWhenBatchExecuting;
                var commandParameterCount = batchCommandBuilder.AdoDbParameters.Count + parameterCount;

                if (maxCount > 0 && commandParameterCount > maxCount)
                {
                    return true;
                }
            }

            return false;
        }

        private class BuilderResult
        {
            public IUpdatable Updatable { get; set; }
            public BatchCommandBuilderResult BatchCommandBuilderResult { get; set; }
            public CommandBuilderContext CommandBuilderContext { get; set; }
            public ModelCommandBuilder ModelCommandBuilder { get; set; }
            public SqlCommandBuilder SqlCommandBuilder { get; set; }
            public ParameterNameGenerator NameGeneratorForSql { get; set; }
            public ParameterNameGenerator NameGeneratorForModel { get; set; }

            public void Reset(DataContext dataContext)
            {
                this.CommandBuilderContext = new CommandBuilderContext();

                this.BatchCommandBuilderResult =
                    new BatchCommandBuilderResult(this.CommandBuilderContext);

                this.ModelCommandBuilder
                    = new ModelCommandBuilder(dataContext, this.CommandBuilderContext);

                this.SqlCommandBuilder
                    = new SqlCommandBuilder(dataContext, this.CommandBuilderContext);
            }
        }
    }
}
